#include "basic.h"
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include "io.h"
#include "List/simclist_impl.h"

void tcp_routine(void *tcp_routine_arguments);
void update_routine(void *update_routine_arguments);
void ping_routine(list_t *superpeer_list);

void send_list(list_t *list, int socket);

struct boot_struct {
	in_addr_t ip;
	int socket;
	list_t *list;
};

typedef struct boot_struct boot_struct_t;

struct update_struct {
	int socket;
	list_t *list;
};

typedef struct update_struct update_struct_t;
pthread_mutex_t myMutex;

int main(int argc, char **argv) {
	fd_set rset; // SELECT
	int maxd;

	int listensd, connsd; // TCP --> richiesto boot o richiesta lista
	struct sockaddr_in servaddr_tcp, cliaddr_tcp;
	socklen_t len_tcp;

	int sockpeer; //  UDP --> aggiornamento peer associati a superpeer

	struct sockaddr_in servaddr_udp;

	len_tcp = sizeof(cliaddr_tcp);

	//prova lock
	pthread_mutex_init(&myMutex, NULL);

	list_t superpeer_list; // lista superpeer
	list_init(&superpeer_list); // inizializzazione
	list_attributes_comparator(&superpeer_list, ip_comparator);
	list_attributes_seeker(&superpeer_list, superpeer_ip_seeker);
	list_attributes_serializer(&superpeer_list,
			element_serializer_superpeer_list);
	list_attributes_copy(&superpeer_list, list_superpeer_meter, 1);

	if ((listensd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { // TCP
		perror("errore in socket riavviare bootstrap");
		exit(1);
	}

	memset((void *) &servaddr_tcp, 0, sizeof(servaddr_tcp));
	servaddr_tcp.sin_family = AF_INET;
	servaddr_tcp.sin_port = htons(BOOT_WELCOME_PORT);
	servaddr_tcp.sin_addr.s_addr = htonl(INADDR_ANY);

	if ((bind(listensd, (struct sockaddr *) &servaddr_tcp, sizeof(servaddr_tcp)))
			< 0) {
		perror("errore in bind");
		exit(1);
	}

	if (listen(listensd, BACKLOG) < 0) {
		perror("errore in listen");
		exit(1);
	}

	maxd = listensd;

	if ((sockpeer = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { // UDP
		perror("errore in socket riavviare bootstrap\n");
		exit(1);
	}

	memset((void *) &servaddr_udp, 0, sizeof(servaddr_udp));
	servaddr_udp.sin_family = AF_INET;
	servaddr_udp.sin_port = htons(BOOT_UPDATE_PORT);
	servaddr_udp.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(sockpeer, (struct sockaddr *) &servaddr_udp, sizeof(servaddr_udp))
			< 0) {
		perror("errore in bind riavviare bootstrap\n");
		exit(1);
	}

	FD_ZERO(&rset);
	FD_SET(sockpeer, &rset);
	if (listensd > maxd)
		maxd = listensd;
	FD_SET(listensd, &rset);

	if (sockpeer > maxd)
		maxd = sockpeer;

	size_t lunghezza_lista;
	pthread_mutex_lock(&myMutex);
	if (list_restore_file(&superpeer_list, "TEMP_LIST", &lunghezza_lista) < 0) {
		printf("Non ci sono superpeer dalla connessione precedente\n");
	} else {
		printf("La lista ha elementi %d\n", list_size(&superpeer_list));
		printf("LISTA SUPERPEER PRESENTI\n");
		print_superpeer_list(&superpeer_list);
		printf("---------------\n");
		//Poiche' esistono superpeer dalla connessione precedente, devo lanciare il thread di ping per eliminare quelli caduti nel frattempo
		pthread_t t1;
		pthread_create(&t1, NULL, (void*) &ping_routine, &superpeer_list);
	}
	pthread_mutex_unlock(&myMutex);

	update_struct_t update_arg;
	update_arg.socket = sockpeer;
	update_arg.list = &superpeer_list;

	pthread_t t2;
	pthread_create(&t2, NULL, (void*) &update_routine, &update_arg);

	for (;;) { // processo principale su accept per fase di boot

		if ((connsd = accept(listensd, (struct sockaddr *) &cliaddr_tcp,
				&len_tcp)) < 0) {
			perror("errore in accept riavviare bootstrap\n");
			exit(1);
		}

		printf("CONNESSIONE INSTAURATA CON: ");
		printf("%s\n", inet_ntoa(cliaddr_tcp.sin_addr));
		fflush(stdout);

		boot_struct_t arguments;
		arguments.ip = cliaddr_tcp.sin_addr.s_addr;
		arguments.socket = connsd;
		arguments.list = &superpeer_list;

		pthread_t t1;
		pthread_create(&t1, NULL, (void*) &tcp_routine, (void*) &arguments);

	}
}

void tcp_routine(void *tcp_routine_arguments) {
	size_t dump_len;
	boot_struct_t *arg = (boot_struct_t*) tcp_routine_arguments;
	int is_superpeer = 0;
	int request_type = -1; // 1 voglio lista, 0 procedura di boot per peer nella rete, -1 e' caduto il mio superpeer, rifaccio boot
	in_addr_t deadIP; // indirizzo IP
	int size = list_size(arg->list);

	if ((read(arg->socket, &request_type, sizeof(int))) < 0) {
		printf("errore in read su tcp routine");
		return;
	}

	if (request_type == 1) { // l'ip connesso vuole la lista
		send_list(arg->list, arg->socket);
	}

	else if (request_type <= 0) { // l'ip connesso deve svolgere la fase di boot

		if (request_type == -1) {
			//il join e' conseguente alla caduta del SP
			//	read per leggere l'indirizzo
			if ((read(arg->socket, &deadIP, sizeof(in_addr_t))) < 0) {
				printf("errore in read su request type");
				return;
			}
			struct in_addr removingIP;
			removingIP.s_addr = deadIP;
			printf("removing IP: %s\n", inet_ntoa(removingIP));
			printf("sto rimuovendo il superpeer\n");

			//	procedura di rimozione
			pthread_mutex_lock(&myMutex);
			delete_superpeer(arg->list, deadIP);
			pthread_mutex_unlock(&myMutex);

			printf("LISTA SUPERPEER PRESENTI\n");
			print_superpeer_list(arg->list);
			printf("---------------\n");

		}

		pthread_mutex_lock(&myMutex);
		list_attributes_comparator(arg->list, peer_comparator);
		list_sort(arg->list, 1);
		elem_superpeer_t *first_element = list_get_at(arg->list, 0);
		list_attributes_comparator(arg->list, ip_comparator);
		pthread_mutex_unlock(&myMutex);
		if (list_size(arg->list) == 0 || (first_element->peer_number)
				> MAX_PEER_NUMBER) { // ogni superpeer ha max 10 peer associati, la rete tende a stabilizzarsi

			is_superpeer = 1;
			pthread_mutex_lock(&myMutex);
			add_superpeer(arg->list, arg->ip);
			if (list_dump_file(arg->list, "TEMP_LIST", &dump_len) < 0) {
				printf("ERRORE IN list_dump_file\n");
				return;
			}

			if ((write(arg->socket, &is_superpeer, sizeof(int))) < 0) {
				perror("errore in write su tcp routine");
				return;
			}

			if ((write(arg->socket, &(arg->ip), sizeof(in_addr_t))) < 0) {
				perror("errore in write su tcp routine");
				return;
			}

			printf("LISTA SUPERPEER PRESENTI\n");
			print_superpeer_list(arg->list);
			printf("---------------\n");
			pthread_mutex_unlock(&myMutex);
			return;

		} else {

			if ((write(arg->socket, &is_superpeer, sizeof(int))) < 0) {
				perror("errore in write su boot routine");
				exit(1);
			}

			if ((write(arg->socket, &(arg->ip), sizeof(in_addr_t))) < 0) {
				perror("errore in write su tcp routine");
				return;
			}

			send_list(arg->list, arg->socket);

		}
	}
	return;
}

void update_routine(void *update_routine_arguments) {
	update_struct_t *arg = update_routine_arguments;
	int n = 0;
	struct sockaddr_in cliaddr_udp;
	size_t len_udp = sizeof(cliaddr_udp);
	int update_number = -1;
	while (1) {
		n = recvfrom(arg->socket, &update_number, sizeof(int), 0,
				(struct sockaddr *) &cliaddr_udp, &len_udp);
		if (n < 0) {
			printf("Errore in recv aggiornamento peer\n");
			continue;
		}

		pthread_mutex_lock(&myMutex);
		update_peer_number(arg->list, cliaddr_udp.sin_addr.s_addr,
				update_number);
		pthread_mutex_unlock(&myMutex);
		printf("UPDATE PEER RICEVUTO \n");
	}
}

void send_list(list_t *list, int socket) {
	size_t dump_len;
	int n;
	pthread_mutex_lock(&myMutex);
	if (list_dump_file(list, "TEMP_LIST", &dump_len) < 0) {
		perror("ERRORE IN list_dump_file");
		return;
	}
	int file_list = open("TEMP_LIST", O_RDWR, 0666);
	size_t lunghezza = lseek(file_list, 0, SEEK_END);
	lseek(file_list, 0, SEEK_SET);
	if ((write(socket, &lunghezza, sizeof(size_t))) < 0) { // scrivo la lunghezza del lista
		perror("errore in write su send list");
		return;
	}
	void *mem = malloc(1000);
	while ((n = read(file_list, mem, 1000)) > 0) {
		if ((writen(socket, mem, n)) < 0) { // invio a blocchi di 1000 byte
			printf("errore in write su socket");
			return;
		}
		printf("INVIATI: %d BYTE\n", n);
		memset(mem, 0, 1000);
	}
	printf("%s\n", "INVIATA LISTA");
	close(file_list);
	pthread_mutex_unlock(&myMutex);
	free(mem);
}

void ping_routine(list_t *superpeer_list) {

	int number_timeout = 0;

	int k, i, s, n, rcv;
	struct sockaddr_in spaddr;
	struct sockaddr_in boot_ping_address;
	struct timeval timeout; //timeout per la select
	fd_set rset; //insieme di descrittori per la select

	// apro la socket
	int sockfd;
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("errore in socket riavviare bootstrap");
		exit(1);
	}

	memset((void *) &boot_ping_address, 0, sizeof(boot_ping_address));
	boot_ping_address.sin_family = AF_INET;
	boot_ping_address.sin_port = htons(BOOT_PING_PORT);
	boot_ping_address.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(sockfd, (struct sockaddr*) &boot_ping_address,
			sizeof(boot_ping_address)) < 0) {
		perror("errore nella bind riavviare bootstrap");
		exit(-1);

	}

	memset((void *) &spaddr, 0, sizeof(spaddr));
	spaddr.sin_family = AF_INET;
	spaddr.sin_port = htons(PING_PORT);

	sleep(3);

	if (list_size(superpeer_list) != 0) {

		list_t lista;
		list_init(&lista);
		list_attributes_serializer(&lista, element_serializer_superpeer_list);
		list_attributes_seeker(&lista, superpeer_ip_seeker);
		list_attributes_comparator(&lista, ip_comparator);
		list_attributes_copy(&lista, list_superpeer_meter, 1);

		list_superpeer_copy(superpeer_list, &lista);

		k = list_size(&lista);
		int array[k];

		//inizializzo l'array temporaneo con valori tutti a 0 ( 0----->non struct sockaddr_in servaddr_udp, cliaddr_udp;connesso; 1------>connesso)

		for (i = 0; i < k; i++) {

			array[i] = 0;
		}

		// ciclo di sendto

		int ping_number;
		ritenta_ping: for (ping_number = 0; ping_number < 6; ping_number++) {
			int h = 0;
			list_iterator_start(&lista);
			while (list_iterator_hasnext(&lista)) {

				elem_superpeer_t * corrente =
						(elem_superpeer_t*) list_iterator_next(&lista);

				spaddr.sin_addr.s_addr = corrente->ip;
				usleep(100 * 1000);
				if (sendto(sockfd, &h, sizeof(int), 0,
						(struct sockaddr *) &spaddr, sizeof(spaddr)) < 0) {
					printf("errore in sendto ping, continuo\n"); // non chiudere sgravo
				} else
					//	(printf("inviato %d\n", h));

					h++;

			}
			list_iterator_stop(&lista);
		}

		/*preparazione alla select*/

		FD_ZERO(&rset);
		FD_SET(sockfd, &rset);

		while (1) {

			timeout.tv_sec = 0;
			timeout.tv_usec = 700000;

			s = select(FD_SETSIZE, &rset, NULL, NULL, &timeout);

			if (s == 0) {
				if (number_timeout < 3) { // eseguito sempre perche' esco dal while col timeout
					number_timeout++;
					goto ritenta_ping;
				} else
					break;
			}

			if (FD_ISSET(sockfd, &rset)) {
				n = recvfrom(sockfd, &rcv, sizeof(int), 0, NULL, NULL);
				if (n < 0) {
					printf("errore in recvfrom, continuo\n");
				}

			}
			array[rcv] = 1;

		}

		number_timeout = 0;

		int f;

		for (f = 0; f < k; f++) {

			if (array[f] == 0) {

				elem_superpeer_t* temp = (elem_superpeer_t*) list_get_at(
						&lista, f);

				struct in_addr a;
				a.s_addr = temp->ip;

				n = list_locate(superpeer_list, &temp->ip);
				pthread_mutex_lock(&myMutex);
				delete_superpeer(superpeer_list, a.s_addr);
				pthread_mutex_unlock(&myMutex);
			}

		}

		printf("LISTA SUPERPEER PRESENTI\n");
		print_superpeer_list(superpeer_list);
		printf("---------------\n");

		printf("\n");

	} else
		printf("la lista e' vuota\n");

}
